/*
 * ========THE SOLMIX PROJECT=====================================
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.gnu.org/licenses/ 
 * or see the FSF site: http://www.fsf.org. 
 */

package org.solmix.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

import javax.sql.DataSource;

import org.apache.commons.dbcp.PoolableConnection;
import org.solmix.api.exception.SLXException;
import org.solmix.api.pool.PoolService;
import org.solmix.api.pool.PoolServiceFactory;
import org.solmix.api.types.Texception;
import org.solmix.api.types.Tmodule;
import org.solmix.commons.collections.DataTypeMap;
import org.solmix.commons.logs.Logger;
import org.solmix.commons.util.DataUtil;
import org.solmix.fmk.util.ServiceUtil;
import org.solmix.sql.internal.SQLConfigManager;

/**
 * 
 * @author solomon
 * @version $Id$ 2011-3-20
 */
@SuppressWarnings("unchecked")
public class ConnectionManager
{

    public PoolService manager;

    private static ConnectionManager instance;

    private final Logger log = new Logger(ConnectionManager.class.getName());

    public static Map referencedDatabases = new HashMap();

    private DataTypeMap thisConfig = null;

    private static PoolServiceFactory poolServiceFactory;

    protected boolean monitorConnections;

    protected long recycleMillis;

    protected static Map<Connection, Map<String, Object>> openConnections;

    @Deprecated
    public ConnectionManager()
    {

    }

    public void init() {
        manager = poolServiceFactory.createPoolService("sql", new PoolableSQLConnectionFactory());
        thisConfig = SQLConfigManager.getConfig();
        monitorConnections = thisConfig.getBoolean("monitorOpenConnections", false);
        recycleMillis = thisConfig.getLong("forceConnectionClosedPeriod", 30000);
        openConnections = new ConcurrentHashMap<Connection, Map<String, Object>>();
        if (monitorConnections) {
            Runnable monitor = new Runnable() {

                Lock lock = new java.util.concurrent.locks.ReentrantLock();

                @Override
                public void run() {
                    Map<Connection, Map<String, Object>> localCopy = null;
                    do {
                        lock.lock();
                        localCopy = new ConcurrentHashMap<Connection, Map<String, Object>>(openConnections);
                        lock.unlock();
                        long now = System.currentTimeMillis();
                        Iterator<Connection> i = localCopy.keySet().iterator();
                        do {
                            if (!i.hasNext())
                                break;
                            Connection conn = (Connection) i.next();
                            Map<String, Object> info = localCopy.get(conn);
                            long ms = ((Long) info.get("time")).longValue();
                            if (now - ms > recycleMillis) {
                                String error = (new StringBuilder()).append("Connection '").append(conn.hashCode()).append("', of type '").append(
                                    info.get("type")).append("', borrowed by the ").append("following call stack, has been open for ").append(
                                    "more than ").append(recycleMillis).append("ms; it will ").append("now be forcibly closed").toString();
                                StackTraceElement elements[] = (StackTraceElement[]) (StackTraceElement[]) info.get("stacktrace");
                                boolean start = false;
                                for (int j = 0; j < elements.length; j++) {
                                    if (!start && elements[j].toString().indexOf("writeOpenConnectionEntry") != -1) {
                                        start = true;
                                        continue;
                                    }
                                    if (start)
                                        error = (new StringBuilder()).append(error).append("\n").append(elements[j].toString()).toString();
                                }

                                log.warn(error);
                                try {
                                    free(conn);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        } while (true);
                        try {
                            Thread.sleep(2000L);
                        } catch (Exception ignored) {
                        }
                    } while (true);
                }
            };
            (new Thread(monitor)).start();
        }
    }

    public static ConnectionManager getInstance() {
        if (instance == null) {
            instance = new ConnectionManager();
            instance.init();
        }
        return instance;
    }

    public static Connection getConnection() throws SLXException {
        return getConnection(null);
    }

    public Connection get() throws SLXException {
        return get(null);
    }

    public Connection get(String dbName) throws SLXException {
        if (DataUtil.isNullOrEmpty(dbName))
            dbName = SQLConfigManager.defaultDatabase;
        Connection __return = null;
        try {
            DataTypeMap dbConfig = thisConfig.getSubtree(dbName);
            Boolean usedPool = dbConfig.getBoolean(SQLDataSource.USED_POOL);
            if (usedPool == null || !usedPool.booleanValue())
                __return = (Connection) manager.borrowObject(dbName);
            else {
                String _interfaceType = dbConfig.getString(SQLDataSource.INTERFACE_TYPE);
                if (EInterfaceType.JNDIOSGI.value().equals(_interfaceType)) {
                    String filterName = dbConfig.getString("jndiosgi");

                    DataSource ds = lookupDataSource(filterName);
                    __return = ds.getConnection();
                } else if (EInterfaceType.OSGI.value().equals(_interfaceType)) {
                    String filterName = dbConfig.getString("osgi");
                    List<DataSource> objs = ServiceUtil.getOSGIServices(DataSource.class, filterName);
                    DataSource obj = null;
                    if(objs!=null&&objs.size()==1)
                        obj=objs.get(0);
                    if (obj != null) {
                        return ((DataSource) obj).getConnection();
                    } else {
                        throw new SLXException(Tmodule.SQL, Texception.OBJECT_TYPE_NOT_ADAPTED, "can not find a adapter for datasource");
                    }
                }
            }
        } catch (SQLException e) {
            throw new SLXException(Tmodule.SQL, Texception.SQL_SQLEXCEPTION, e.getMessage());
        }
        // markAsReferenced(dbName);
        writeOpenConnectionEntry(__return, "get");
        return __return;
    }

    /**
     * @param dbName
     * @return
     */
    public static Connection getConnection(String dbName) throws SLXException {
        ConnectionManager m = ConnectionManager.getInstance();
        return m.get(dbName);

    }

    /**
     * for JNDI lookup datasource in the context.
     * 
     * @return
     */
    private static DataSource lookupDataSource(String filterName) throws SLXException {
        Object obj = ServiceUtil.getOsgiJndiService(DataSource.class.getName(), filterName);
        if (obj instanceof DataSource) {
            return (DataSource) obj;
        } else {
            throw new SLXException(Tmodule.SQL, Texception.OBJECT_TYPE_NOT_ADAPTED, "can not find a adapter for datasource");
        }

    }

    /**
     * @param return1
     * @param string
     */

    private void writeOpenConnectionEntry(Connection conn, String type) {
        if (monitorConnections) {
            Map<String, Object> openConnEntry = new HashMap<String, Object>();
            openConnEntry.put("type", type);
            openConnEntry.put("time", Long.valueOf(System.currentTimeMillis()));
            openConnEntry.put("stacktrace", Thread.currentThread().getStackTrace());
            openConnections.put(conn, openConnEntry);
        }

    }

    public static void freeConnection(Connection conn) throws SLXException {
        ConnectionManager m = ConnectionManager.getInstance();
        m.free(conn);
    }

    /**
     * @param connection
     * @throws SLXException
     */
    public void free(Connection conn) throws SLXException {
        try {
            if (conn == null)
                return;
            if (conn != null && !conn.isClosed() && !conn.getAutoCommit())
                conn.commit();
            if (conn instanceof PoolableConnection) {
                if (!conn.isClosed())
                    ((PoolableConnection) conn).reallyClose();
            } else if (conn != null && !conn.isClosed())
                conn.close();
            if (monitorConnections)
                openConnections.remove(conn);
        } catch (SQLException e) {
            log.error("Error attempting to commit and close a connection");
            throw new SLXException(Tmodule.SQL, Texception.SQL_SQLEXCEPTION, e);
        }

    }

    public static Connection getNewConnection() throws SLXException {
        return getNewConnection(null);
    }

    public Connection getNew() throws SLXException {
        return getNew(null);
    }

    public Connection getNew(String dbName) throws SLXException {
        Connection __return;
        if (dbName == null)
            dbName = SQLConfigManager.defaultDatabase;
        try {
            __return = (Connection) manager.borrowNewObject(dbName);
        } catch (Exception e) {
            throw new SLXException(Tmodule.SQL, Texception.POOL_BORROW_OBJECT_FAILD, e);
        }
        writeOpenConnectionEntry(__return, "getNew");
        return __return;
    }

    /**
     * @return
     * @throws SLXException
     */
    public static Connection getNewConnection(String dbName) throws SLXException {
        ConnectionManager m = ConnectionManager.getInstance();
        return m.getNew(dbName);
    }

    /**
     * @return the poolServiceFactory
     */
    public PoolServiceFactory getPoolServiceFactory() {
        return poolServiceFactory;
    }

    /**
     * @param poolServiceFactory the poolServiceFactory to set
     */
    public void setPoolServiceFactory(PoolServiceFactory poolServiceFactory) {
        ConnectionManager.poolServiceFactory = poolServiceFactory;
    }
}
